package org.systemsbiology.bigquery.gae.dao;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import org.systemsbiology.addama.commons.gae.dataaccess.MemcacheLoaderCallback;
import org.systemsbiology.addama.commons.gae.dataaccess.callbacks.PutEntityTransactionCallback;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.UUID;
import java.util.logging.Logger;

import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService;
import static com.google.appengine.api.datastore.KeyFactory.createKey;
import static com.google.appengine.api.memcache.Expiration.byDeltaSeconds;
import static com.google.appengine.api.memcache.MemcacheServiceFactory.getMemcacheService;
import static java.util.UUID.randomUUID;
import static org.apache.commons.lang.StringUtils.equalsIgnoreCase;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.systemsbiology.addama.commons.gae.dataaccess.DatastoreServiceTemplate.inTransaction;
import static org.systemsbiology.addama.commons.gae.dataaccess.MemcacheServiceTemplate.loadIfNotExisting;

/**
 * @author hrovira
 */
public class Channels {
    private static final Logger log = Logger.getLogger(Channels.class.getName());
    private static final String COOKIE_NAME = "x-systemsbiology-bq-gae-channel";

    public static String newChannelKey(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String existingUUID = lookupClientUUID(request);
        if (!isEmpty(existingUUID)) {
            return existingUUID;
        }

        String clientUUID = UUID.randomUUID().toString();
        response.addCookie(new Cookie(COOKIE_NAME, clientUUID));

        MemcacheLoaderCallback callback = new ChannelsMemcacheLoaderCallback();
        return (String) loadIfNotExisting(getMemcacheService("channelkeys"), clientUUID, callback, byDeltaSeconds(3600));
    }

    public static String lookupChannelKey(HttpServletRequest request) throws Exception {
        String clientUUID = lookupClientUUID(request);
        if (isEmpty(clientUUID)) {
            return null;
        }

        MemcacheLoaderCallback callback = new LookupChannelsMemcacheLoaderCallback();
        return (String) loadIfNotExisting(getMemcacheService("channelkeys"), clientUUID, callback, byDeltaSeconds(3600));
    }

    /*
     * Private Methods
     */
    private static String lookupClientUUID(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (equalsIgnoreCase(cookie.getName(), COOKIE_NAME) && !isEmpty(cookie.getValue())) {
                    return cookie.getValue();
                }
            }
        }

        return null;
    }

    /*
    * Channel Key Loaders
    */
    private static class LookupChannelsMemcacheLoaderCallback implements MemcacheLoaderCallback {
        public Serializable getCacheableObject(String clientUUID) throws Exception {
            try {
                Entity e = getDatastoreService().get(createKey("channels", clientUUID));
                return e.getProperty("channel_key").toString();
            } catch (EntityNotFoundException e) {
                log.warning(e.getMessage());
            }
            return null;
        }
    }

    private static class ChannelsMemcacheLoaderCallback extends LookupChannelsMemcacheLoaderCallback {
        public Serializable getCacheableObject(String clientUUID) throws Exception {
            String channelKey = (String) super.getCacheableObject(clientUUID);
            if (!isEmpty(channelKey)) {
                return channelKey;
            }

            log.info("create new channel key for " + clientUUID);

            channelKey = randomUUID().toString();

            Entity e = new Entity(createKey("channels", clientUUID));
            e.setProperty("channel_key", channelKey);
            inTransaction(getDatastoreService(), new PutEntityTransactionCallback(e));
            return channelKey;
        }
    }
}
